package com.bpm.stringsimulator.sound;

import javax.sound.midi.Instrument;
import javax.sound.midi.MidiChannel;
import javax.sound.midi.MidiSystem;
import javax.sound.midi.MidiUnavailableException;
import javax.sound.midi.Synthesizer;

import com.bpm.stringsimulator.model.FrequencyComputationException;
import com.bpm.stringsimulator.model.GuitarString;

/**
 * Syntezator dzwieku. Potrafi grac na wybranych instrumentach.
 * 
 * @author piotrs10
 * 
 */
public class SoundSynthesizer {

	/**
	 * Instrument gitary
	 */
	private static final int GUITAR = 24;

	private static final int CHANNEL = 4;

	private static final double FREQUENCY_OF_A = 440;

	/**
	 * Dokonuje synteze dzwieku struny gitary na podstawie jej parametrow,
	 * poziomu glosnosci i czasu trwania dzwieku.
	 * 
	 * @param string
	 *            Struna.
	 * @param volume
	 *            Poziom glosnosci 0 - 127
	 * @param duration
	 *            Czas trwania (ms)
	 * @throws SyntheteiserException
	 */
	public void playGuitarString(GuitarString string, int volume, int duration)
			throws SyntheteiserException {
		Synthesizer midiSynthesizer = null;
		try {
			// otwieramy syntezator
			midiSynthesizer = MidiSystem.getSynthesizer();
			midiSynthesizer.open();
			// pobieramy kanaly urzadzenia
			MidiChannel[] channels = midiSynthesizer.getChannels();
			// pobieramy i ladujemy instrument
			Instrument[] availableInstruments = midiSynthesizer
					.getAvailableInstruments();
			midiSynthesizer.loadInstrument(availableInstruments[GUITAR]);
			// odtwarzamy dzwiek przez zadany czas
			double frequency = string.getFrequency();
			int midiNumber = getMidiNumberByFrequency(frequency);
			channels[CHANNEL].noteOn(midiNumber, volume);
			Thread.sleep(duration);
			channels[CHANNEL].allNotesOff();
		} catch (MidiUnavailableException e) {
			throw new SyntheteiserException("Blad, urzadzenie niedostepne!", e);
		} catch (FrequencyComputationException e) {
			throw new SyntheteiserException(
					"Blad, blad obliczania czestotliwosci!", e);
		} catch (InterruptedException e) {
			throw new SyntheteiserException("Odtwarzanie przerwane!", e);
		} finally {
			if (midiSynthesizer != null) {
				midiSynthesizer.close();
			}
		}

	}

	/**
	 * Oblicza nr midi na podstawie czestotliwosci, zgodnie z instrukcja:
	 * http://www.phys.unsw.edu.au/jw/notes.html
	 * 
	 * @param frequency
	 *            Czestotliwosc
	 * @return nr midi
	 */
	private int getMidiNumberByFrequency(double frequency) {

		return (int) (12 * log((frequency / FREQUENCY_OF_A), 2) + 69);
	}

	/**
	 * Oblicza logarytm dowolnej podstawy
	 * 
	 * @param x
	 *            wartosc
	 * @param base
	 *            Podstawa,
	 * @return wynik
	 */
	private double log(double x, int base) {
		return (Math.log(x) / Math.log(base));
	}

}
